/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.factory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import org.modelcc.Position;
import org.modelcc.SeparatorPolicy;
import org.modelcc.language.metamodel.CompositeLanguageElement;
import org.modelcc.language.metamodel.LanguageMember;
import org.modelcc.language.metamodel.MemberPositionMetadata;

/**
 * Member specification factory
 * 
 * @author Luis Quesada (lquesada@modelcc.org), refactored by Fernando Berzal (berzal@modelcc.org) 
 */
public class MemberSpecificationFactory 
{
	// Process composite element
	
	public List<MemberSpecification> create (CompositeLanguageElement ce) 
	{
		List<List<MemberSpecification>> nodes = new ArrayList<List<MemberSpecification>>();
		
		nodes.add(new ArrayList<MemberSpecification>());

		for (LanguageMember current: ce.getMembers())
			nodes = processMember(nodes, current);
            
		for (Entry<LanguageMember,MemberPositionMetadata> currentPosition: ce.getPositions().entrySet())
			nodes = processPosition(nodes, currentPosition);

		if (ce.isFreeOrder())
			nodes = processFreeOrder(nodes);
		
		return processContent(nodes);
	}
	
	private List<MemberSpecification> processContent (List<List<MemberSpecification>> nodes) 
	{
		List<MemberSpecification> result = new ArrayList<MemberSpecification>();
		
		for (List<MemberSpecification> current: nodes) {
			MemberSpecification mn = new MemberSpecification();
			for (MemberSpecification curMemberNode: current)
				mn.add(curMemberNode);
			result.add(mn);
		}
		
		return result;
	}

	private List<List<MemberSpecification>> processPosition (
			List<List<MemberSpecification>> nodes,
			Entry<LanguageMember, MemberPositionMetadata> currentPosition ) 
	{
		MemberPositionMetadata posInfo = currentPosition.getValue();
		LanguageMember source = currentPosition.getKey();
		LanguageMember target = posInfo.getMember();
		List<List<MemberSpecification>> newNodes = new ArrayList<List<MemberSpecification>>();
		for (List<MemberSpecification> curNodes: nodes) {
			if (posInfo.contains(Position.BEFORE)) {
				processBefore(newNodes,curNodes,source,target);
			}
			if (posInfo.contains(Position.AFTER)) {
				processAfter(newNodes,curNodes,source,target);
			}
			if (posInfo.contains(Position.WITHIN)) {
				processInside(newNodes,curNodes,source,target,Position.WITHIN,posInfo.getSeparatorPolicy());
			}
			else if (posInfo.contains(Position.BEFORELAST)) {
				processInside(newNodes,curNodes,source,target,Position.BEFORELAST,posInfo.getSeparatorPolicy());
			}
			processNoSource(newNodes,curNodes,source,target);
		}
		return newNodes;
	}

	private void processAfter (
			List<List<MemberSpecification>> newNodes,
			List<MemberSpecification> curNodes, 
			LanguageMember source,
			LanguageMember target ) 
	{
		int sourceIndex = searchFront(curNodes,source);
		int targetIndex = searchBack(curNodes,target);
		if (sourceIndex != -1 && targetIndex != -1) {
        	MemberSpecification mn = new MemberSpecification(curNodes.get(targetIndex));
        	mn.clear();
    		mn.addAll(curNodes.get(targetIndex).getMembers());
    		mn.addAll(curNodes.get(sourceIndex).getMembers());
        	List<MemberSpecification> curNodesCopy = new ArrayList<MemberSpecification>();
        	curNodesCopy.addAll(curNodes);
    		curNodesCopy.set(targetIndex,mn);
    		curNodesCopy.remove(sourceIndex);
    		newNodes.add(curNodesCopy);
		}
	}

	private void processBefore (
			List<List<MemberSpecification>> newNodes,
			List<MemberSpecification> curNodes, 
			LanguageMember source,
			LanguageMember target ) 
	{
		int sourceIndex = searchBack(curNodes,source);
		int targetIndex = searchFront(curNodes,target);
		if (sourceIndex != -1 && targetIndex != -1) {
        	MemberSpecification mn = new MemberSpecification(curNodes.get(targetIndex));
        	mn.clear();
    		mn.addAll(curNodes.get(sourceIndex).getMembers());
    		mn.addAll(curNodes.get(targetIndex).getMembers());
        	List<MemberSpecification> curNodesCopy = new ArrayList<MemberSpecification>();
        	curNodesCopy.addAll(curNodes);
    		curNodesCopy.set(targetIndex,mn);
    		curNodesCopy.remove(sourceIndex);
    		newNodes.add(curNodesCopy);
		}
	}

	private void processInside ( 
			List<List<MemberSpecification>> newNodes,
			List<MemberSpecification> curNodes, 
			LanguageMember source, 
			LanguageMember target,
			int position, 
			SeparatorPolicy separatorPolicy ) 
	{
		int sourceIndex = searchBack(curNodes,source);
		int targetIndex = searchFront(curNodes,target);
		if (sourceIndex != -1 && targetIndex != -1) {
        	MemberSpecification mn = new MemberSpecification(curNodes.get(targetIndex));
        	mn.setContent(target,new MemberContent(position,separatorPolicy,source));
        	List<MemberSpecification> curNodesCopy = new ArrayList<MemberSpecification>();
        	curNodesCopy.addAll(curNodes);
    		curNodesCopy.set(targetIndex,mn);
    		curNodesCopy.remove(sourceIndex);
    		newNodes.add(curNodesCopy);
		}		
	}

	private void processNoSource (
			List<List<MemberSpecification>> newNodes,
            List<MemberSpecification> curNodes,
            LanguageMember source,
            LanguageMember target ) 
    {
	    int sourceIndex = searchBack(curNodes,source);
	    if (sourceIndex == -1) {
		    List<MemberSpecification> curNodesCopy = new ArrayList<MemberSpecification>();
		    curNodesCopy.addAll(curNodes);
		    newNodes.add(curNodesCopy);
	    }
	}
	
	private List<List<MemberSpecification>> processMember (
			List<List<MemberSpecification>> nodes, LanguageMember current ) 
	{
		List<List<MemberSpecification>> newNodes = new ArrayList<List<MemberSpecification>>();
		for (List<MemberSpecification> curNodes: nodes) {
			List<MemberSpecification> noOpt = new ArrayList<MemberSpecification>();
			for (int i = 0;i < curNodes.size();i++)
				noOpt.add(new MemberSpecification(curNodes.get(i)));
			noOpt.add(new MemberSpecification(current));
			newNodes.add(noOpt);
			if (current.isOptional())
				newNodes.add(curNodes);
		}
		return newNodes;
	}

	private List<List<MemberSpecification>> processFreeOrder (List<List<MemberSpecification>> nodes) 
	{
		List<List<MemberSpecification>> newNodes = new ArrayList<List<MemberSpecification>>();
		
		for (List<MemberSpecification> current: nodes) {
			// Current production
			List<List<MemberSpecification>> combinations = new ArrayList<List<MemberSpecification>>(); 

			// Empty production
			if (current.isEmpty())
				combinations.add(new ArrayList<MemberSpecification>());

			for (MemberSpecification mn: current) {
				if (combinations.isEmpty()) { // If first element, add it
		    		List<MemberSpecification> currentCombination = new ArrayList<MemberSpecification>();
		    		currentCombination.add(mn);
					combinations.add(currentCombination);
				} else { // If other element, add it to every position
		        	List<List<MemberSpecification>> newCombinations = new ArrayList<List<MemberSpecification>>(); 
					for (List<MemberSpecification> currentCombination: combinations) {
						for (int i = 0;i <= currentCombination.size();i++) {
		            		List<MemberSpecification> newCombination = new ArrayList<MemberSpecification>();
		            		newCombination.addAll(currentCombination);
		            		newCombination.add(i,mn);
		            		newCombinations.add(newCombination);
						}
					}
					combinations = newCombinations;
				}
			}
			newNodes.addAll(combinations);
		}
		return newNodes;
	}

	// Search
	
	private int searchBack (List<MemberSpecification> curNodes, LanguageMember source) 
	{
    	int found = -1;
		for (int i = 0;i < curNodes.size();i++) {
			if (curNodes.get(i).getBack() == source) {
				if (found == -1) {
					found = i;
				} else {
					return -1;
				}
			}
		}
		return found;		
	}

    private int searchFront (List<MemberSpecification> curNodes, LanguageMember source) 
    {
    	int found = -1;
		for (int i = 0;i < curNodes.size();i++) {
			if (curNodes.get(i).getFront() == source) {
				if (found == -1) {
					found = i;
				} else {
					return -1;
				}
			}
		}
		return found;		
	}    
}
